﻿//using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System;
using System.IO;
using Urd;
using Urd.Packet;
namespace Urd.Lab
{
    public static class random 
    {
        private static Random R;
        static random()
        {
            R = new Random(123123);
            R.Next();

        }
        public static int get() { return R.Next(); }
    }
    public static class sender
    {
        public class sendinfo
        {
            public packet tosend;
            public packet recieved;
            public IPAddress ip;
            public int port;
            public bool asqired;
            public bool sended;
            public TimeSpan lag;
            public long uid;
            public byte[] data;

            //default
            public sendinfo
                ()
            {
                this.port = 0;
                this.ip = null;
                this.tosend = new packet(Opcode.Undefined);
                this.recieved = new packet(Opcode.Undefined);
                asqired = false;
                sended = false;
                this.uid = -1;
                lag = TimeSpan.Zero;
                data = new byte[64];
            }
            //sendinfo sended
            public sendinfo
                (packet p, IPAddress addr, int port, long uid,byte[]buf=null)
            {
                this.port = port;
                this.ip = addr;
                this.tosend = p;
                this.recieved = new packet(Opcode.Undefined);
                asqired = false;
                sended = false;
                this.uid = uid;
                lag = TimeSpan.Zero;
                data = new byte[64];
            }
            //sendinfo recieved
            public sendinfo
                (packet p, long uid, TimeSpan lag, packet psended, byte[] buf = null)
            {
                this.port = 0;
                this.ip = null;
                this.tosend = psended;
                this.recieved = p;
                asqired = true;
                sended = true;
                this.uid = uid;
                this.lag = lag;
                data = new byte[64];
            }
        }

        public static List<sendinfo> queque = new List<sendinfo>();
        public static Dictionary<IPEndPoint, List<sendinfo>> prepared_queque = new Dictionary<IPEndPoint, List<sendinfo>>();
        private static bool working = false;
        // ManualResetEvent instances signal completion.
        private static ManualResetEvent connectDone =
            new ManualResetEvent(false);
        private static ManualResetEvent sendDone =
            new ManualResetEvent(false);
        private static ManualResetEvent receiveDone =
            new ManualResetEvent(false);
        //		private static Random r = new Random(1234567);

        struct connectinfo
        {
            public TcpClient client;
            public List<sendinfo> queque;
            public DateTime start;
            public int index;
            public NetworkStream stream;
            public byte[] data;
            public connectinfo(ref TcpClient c, ref List<sendinfo> que, int i, NetworkStream ns, byte[] buf = null)
            {
                client = c;
                queque = que;
                index = i;
                start = DateTime.Now;
                stream = ns;
                data = buf;
            }
        }


        private static void connect_callback(IAsyncResult res)
        {
            connectinfo cinfo = (connectinfo)res.AsyncState;
            cinfo.client.EndConnect(res);
            NetworkStream stream = null;

            //packet ret;
            stream = cinfo.client.GetStream();
            cinfo.stream = stream;
            foreach (sendinfo s in cinfo.queque)
            {
                if (!s.sended)
                {
                    s.sended = true;
                }

                byte[] data = s.tosend.Bytes;

                stream.BeginWrite(data, 0, data.Length, write_callback, new connectinfo(ref cinfo.client, ref cinfo.queque,0,stream));
            }
        }
        private static void write_callback(IAsyncResult res)
        {
            connectinfo cinfo = (connectinfo)res.AsyncState;
            byte[] data = new byte[256];
            cinfo.data = data;
            cinfo.stream.EndWrite(res);
            //waitone
            cinfo.stream.BeginRead(data, 0, data.Length, read_callback, new connectinfo(ref cinfo.client, ref cinfo.queque, 0, cinfo.stream,data));


        }

        private static void read_callback(IAsyncResult res)
        {
            connectinfo cinfo = (connectinfo)res.AsyncState;
            //int i;
            cinfo.stream.EndRead(res);
            //if ((i = stream.Read(data, 0, data.Length)) != 0)
            ///	{
            packet ret = new packet(cinfo.data);
            ////		//Debug.Log(ret.debug);

            //}
            //else
            //		ret = new packet(Opcode.Undefined);
            //	stream.Close();
            //	cinfo.client.Close();
            //}
            ////   Debug.Log(queque[c].tosend.code.ToString() + "|" + (DateTime.Now - start).ToString());

            sendinfo info = new sendinfo(ret, cinfo.queque[cinfo.index].uid, (DateTime.Now - cinfo.start), cinfo.queque[cinfo.index].tosend);
            info.asqired = true;

            lock (cinfo.queque)
            {
                cinfo.queque[cinfo.index] = info;
            }    //Debug.Log(ret.debug);
            cinfo.stream.Close();
            cinfo.client.Close();
        }

        private static void process()
		{
			while (true)
			{
				///prepare
				for (int c = 0; c < queque.Count; c++)
				{
					IPAddress add = queque[c].ip;
					int port = queque[c].port;

					IPEndPoint ipe = new IPEndPoint(add,port);

					if(prepared_queque.ContainsKey(ipe))
					{
						if(!prepared_queque[ipe].Contains(queque[c]))
							prepared_queque[ipe].Add(queque[c]);
					}
					else
					{
                        List<sendinfo> lp = new List<sendinfo>();
						lp.Add(queque[c]);
						prepared_queque.Add(ipe,lp);
					}

				}

				///clean
				/// 

				foreach(KeyValuePair<IPEndPoint,List<sendinfo>> k in prepared_queque)
				{
                    for (int i = 0; i < k.Value.Count;i++ )
                    {
                        if (k.Value[i].asqired) k.Value.Remove(k.Value[i]);
                    }
				}
				foreach (KeyValuePair<IPEndPoint,List<sendinfo>> currentinfo 
                    in prepared_queque)
				{
					
					//DateTime start = DateTime.Now;
					
					
					TcpClient client = null;
					
				//	try
				//	{
						//KeyValuePair<IPEndPoint,List<sendinfo>> currentinfo = prepared_queque.[c];
						List<sendinfo>pqueque = currentinfo.Value;

							
							//lag here
							client = new TcpClient();
							//client.NoDelay=true;
							//client.SendTimeout=1000;
							//client.Connect(queque[c].ip, queque[c].port);
							//NetworkStream ns;
							//byte[] buffer;

							client.BeginConnect(currentinfo.Key.Address, 
						                    currentinfo.Key.Port, connect_callback, 
						                    new connectinfo(ref client, ref pqueque,0,null,null));
							
					//	}
						//else throw new IOException();
					//	else 
					//	{
					//		if(DateTime.Now.Ticks-currentinfo.uid>3*1000*10000)
					//		{
								//удаляем записи старше 2 секунд.
								//Debug.Log("Deleted");
								//lock(queque) {queque.RemoveAt(c);
					//		}
						//}
				//	}
				//	catch (Exception e)
				//	{
						
						
						//if (client != null)
						//	client.Close();
						
						
						//Console.WriteLine("IOException: {0}", e); 
					//}
					
					
				}
				
				
				Thread.Sleep(4);
			}
			
			
		}

        private static Thread worker = new Thread(new ThreadStart(process));

        //returns ticket uid
        public static long put(packet p, IPAddress addr, int port)
        {

            long uid = random.get();
            sendinfo s = new sendinfo(p, addr, port, uid);
            lock (queque)
            {
                if (!queque.Exists(x => x.uid == uid
                                  || (x.tosend.code == p.code && x.tosend.command == p.command && x.tosend.ID == p.ID)
                                  ))
                    queque.Add(s);

            }
            return uid;
        }
        public static void start()
        {
            working = true;
            worker.Start();

        }
        public static void stop()
        {
            working = false;
            worker.Abort();
        }
        public static bool Working { get { return working; } }
        //returns packet by ticket uid
        public static packet get(long uid)
        {
            packet ret = new packet(Opcode.Undefined);

            lock (queque)
            {
                sendinfo s = null;

                if ((s = queque.Find(x => x.uid == uid)) != null && s.recieved != null)
                {
                    ret = s.recieved;
                    //  Debug.Log(ret.debug);
                    if (s.asqired)
                        queque.Remove(s);
                }

            }
            return ret;

        }
        public static packet get(packet p)
        {
            packet ret = new packet(Opcode.Undefined);

            lock (queque)
            {
                sendinfo s = null;

                if ((s = queque.Find(x => x.tosend.code == p.code)) != null && s.recieved != null)
                {
                    ret = s.recieved;
                    //  Debug.Log(ret.debug);
                    if (s.asqired)
                        queque.Remove(s);
                }

            }
            return ret;

        }

    }
}